// @Creation:    30.04.2012
// @Version:     30.05.2012
//
// @cpu:    x86,
// @author:    alexey kurshakov [alexey.kurshakov@gmail.com]
//
// @Contents:    serialport_c.c
//    Функции для работы с последовательным портом
//
#include "serialport.h"

extern void writeDataToGoCOMPort(void* hGoCOMPort, void* data, int dataSize);

// объект - последовательный COM-порт
typedef struct {
	void* hGoCOMPort;  		// указатель на go-структуру
#ifdef WIN32
	HANDLE hSerial;   		// хэндл для работы с портом
	HANDLE hCloseEvent;    	// событие завершение потока читающего данные из порта
#else
#endif
} COMPort;

// Пытаемся открыть COM-порт, конфигурируем его в соотв-ии с настройками
ErrorRes OpenCOMPort(COMPortSettings* settings, void* hGoCOMPort, void** pUnExportedValues)
{
#ifdef WIN32
	// 1. Пытаемcя открыть порт
	LPCSTR portname = settings->devName;
	HANDLE hSerial = CreateFile
	(
		portname,
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_FLAG_OVERLAPPED,
		NULL
	);
	if (hSerial == NULL) {
		// Если не удалось открыть порт с указанным именем,
		// то выходим с ошибкой
		return eUnknownDevice;
	}

	// 2. Конфигурируем порт
	{
		DCB dcbSettings;
		// Инициализируем конфигурацию значениями по умолчанию
		FillMemory(&dcbSettings, sizeof(dcbSettings), 0);
		dcbSettings.DCBlength = sizeof(dcbSettings);
		if (!BuildCommDCB("9600,n,8,1", &dcbSettings))
			return eFail;

		// Меняем некоторую часть переданными настройками
		dcbSettings.BaudRate = settings->baudRate;
		dcbSettings.ByteSize = settings->byteSize;
		dcbSettings.Parity = settings->parity;
		dcbSettings.StopBits = settings->stopBits;
		// seems to me this is pointless for general tasks
		// dcbSettings.fOutxCtsFlow = settings->ctsFlow;
		// dcbSettings.fOutxDsrFlow = settings->dsrFlow;
		// dcbSettings.fRtsControl = settings->rtsControl == 1
		// ? RTS_CONTROL_TOGGLE : RTS_CONTROL_DISABLE;
		// dcbSettings.fDtrControl = settings->dtrControl == 1
		// ? DTR_CONTROL_ENABLE : DTR_CONTROL_DISABLE;

		// Устанавливаем новые настройки
		if (!SetCommState (hSerial, &dcbSettings) ||
			// Устанавливаем ожидание события
			// появление байта в приемнике
			!SetCommMask(hSerial, EV_RXCHAR))
			return eFail;
	}

	// 3. Если все успешно, то порт открыт, возвращаем его дескриптор
	COMPort* ptr = malloc(sizeof(COMPort));
	ptr->hSerial = hSerial;
	ptr->hCloseEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	ptr->hGoCOMPort = hGoCOMPort;
	*pUnExportedValues = (void*)ptr;
#else
#endif
	return eNoError;
}

// Пишем данные в COM-порт
ErrorRes WriteDataToCOMPort(void* pUnExportedValues, void* data, int* dataSize)
{
	COMPort* pCOMPort = (COMPort*)pUnExportedValues;
#ifdef WIN32
	if (data == NULL || *dataSize == 0)
		return eInvalidData;
	ErrorRes res = eNoError;

	OVERLAPPED osStatus = {0};
	DWORD dwWritten;
	DWORD dwToWrite = *dataSize;
	osStatus.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (osStatus.hEvent == NULL)
		return eFail;

	// Пишем данные в порт
	WriteFile(pCOMPort->hSerial, data, dwToWrite, &dwWritten, &osStatus);
	dwWritten = 0;

	if (!GetOverlappedResult(pCOMPort->hSerial, &osStatus, &dwWritten, TRUE))
	{
		// Операция записи завершилась неудачно
		res = eIOError;
	}
	*dataSize = dwWritten;
	CloseHandle(osStatus.hEvent);
	return res;
#else
	return eNoError;
#endif
}

// Читаем данные из COM-порта
ErrorRes readDataFromCOMPort(COMPort* pCOMPort)
{
#ifdef WIN32
	const int readBufferSize = 512;
	BYTE readBuffer[readBufferSize]; // приемник порта
	DWORD dwBytesRead = 0;
	DWORD dwReadSize = readBufferSize;
	BOOL fOverlapped;
	OVERLAPPED ov;
	ZeroMemory(&ov, sizeof(OVERLAPPED));
	ov.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

	// Читаем данные из COM-порта
	if( !ReadFile( pCOMPort->hSerial, readBuffer, dwReadSize, &dwBytesRead, &ov ) )
	{
		fOverlapped = TRUE;
	}
	else
	{
		// Операция выполнилась незамедлительно
		fOverlapped = FALSE;
	}

	if (fOverlapped)
	{
		if (!GetOverlappedResult(pCOMPort->hSerial,
								&ov,
								&dwBytesRead,
								TRUE))
		{
			// Операция записи завершилась неудачно
			CloseHandle(ov.hEvent);
			return eIOError;
		}
	}
	writeDataToGoCOMPort(pCOMPort->hGoCOMPort, readBuffer, dwBytesRead);
	CloseHandle(ov.hEvent);
#else
#endif
	return eNoError;
}

// Поток, ожидающий появление данныех в порту
ErrorRes ReadDataFromCOMPort(void* pUnExportedValues)
{
	COMPort* pCOMPort = (COMPort*)pUnExportedValues;
#ifdef WIN32
	OVERLAPPED ov;
	ZeroMemory(&ov, sizeof(OVERLAPPED));
	ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	while (1)
	{
		DWORD mask;
		if (WaitCommEvent(pCOMPort->hSerial, &mask, &ov))
		{
			// Событие RXCHAR произошло раньше - обрабатываем.
			readDataFromCOMPort(pCOMPort);
		}
		else
		{
			// Событие RXCHAR еще не произошло.
			// Будем ждать, что произойдет раньше - RXCHAR или hTerminate (событие завершения потока).
			HANDLE hHandles[] = {ov.hEvent, pCOMPort->hCloseEvent};
			DWORD dwResult = WaitForMultipleObjects(2, &hHandles[0], FALSE, INFINITE);
			switch (dwResult)
			{
			case WAIT_OBJECT_0:
			{
				// Пришел символ из порта.
				// Читаем данные из порта..
				readDataFromCOMPort(pCOMPort);
				break;
			}

			case WAIT_OBJECT_0+1:
				// Пришла команда потоку завершиться.
				// Завершаемя...
				goto _exit;

			default:
				break;
			}
		}
	}
_exit:
	CloseHandle(ov.hEvent);
	SetCommMask ( pCOMPort->hSerial, 0 );
#else
#endif
	return eNoError;
}

// Закрываем COM-порт, высвобождаем ресурсы
ErrorRes CloseCOMPort(void* pUnExportedValues)
{
	if (pUnExportedValues == NULL)
		return eFail;
	COMPort* pCOMPort = (COMPort*)pUnExportedValues;
#ifdef WIN32
	SetEvent(pCOMPort->hCloseEvent);
	Sleep(250);
	CloseHandle(pCOMPort->hCloseEvent);
	CloseHandle(pCOMPort->hSerial);
	free(pUnExportedValues);
#else
#endif
	return eNoError;
}
